package han.chensing.latos.common.level.util.bind_event;

import java.lang.reflect.Field;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiConsumer;
import java.util.function.Function;

import han.chensing.latos.common.level.content.timeLine.frame.GeneralEventTypes;
import han.chensing.latos.common.level.util.ObjectUtil;

@SuppressWarnings("unchecked")
public class BindEventTypeUtil {
    public static <T,U> void forEachAndOthers(
            Object from,
            Object to,
            Class<T> fromFieldClass,
            Class<U> toFieldClass,
            BiConsumer<T,U> biConsumer){
        forEachAndOthers(from,to,fromFieldClass,toFieldClass,BindEventTypeExtra.DEFAULT_EXTRA,BindEventTypeExtra.DEFAULT_EXTRA,biConsumer);
    }

    public static <T,U> void forEachAndOthers(
            Object from,
            Object to,
            Class<T> fromFieldClass,
            Class<U> toFieldClass,
            int fromTargetExtra,
            int toTargetExtra,
            BiConsumer<T,U> biConsumer) {
        forSelfAndOtherSingle(from,to,fromFieldClass,toFieldClass,null,fromTargetExtra,toTargetExtra,biConsumer);
    }

    public static <T,U> void forSelfAndOtherSingle(
            Object from,
            Object to,
            Class<T> fromFieldClass,
            Class<U> toFieldClass,
            GeneralEventTypes matchGeneralEventTypes,
            int fromTargetExtra,
            int toTargetExtra,
            BiConsumer<T,U> biConsumer){
        if (ObjectUtil.checkNull(from, to, fromFieldClass, toFieldClass, matchGeneralEventTypes, biConsumer)) return;
        doSingle(from,to,fromFieldClass,toFieldClass, matchGeneralEventTypes,fromTargetExtra,toTargetExtra,
                (fromField, toField, eventType) -> {
                    try {
                        fromField.setAccessible(true);
                        toField.setAccessible(true);
                        T fromObject = (T) fromField.get(from);
                        U toObject = (U) toField.get(to);
                        biConsumer.accept(fromObject, toObject);
                    } catch (Exception ignore) {
                    }
                });
    }

    public static <T> void forEach(
            Object from,
            Class<T> fromFieldClass,
            int fromTargetExtra,
            BiConsumer<GeneralEventTypes,T> consumer) {
        forSingle(from,fromFieldClass,null,fromTargetExtra,consumer);
    }

    public static <T> void forSingle(
            Object from,
            Class<T> fromFieldClass,
            GeneralEventTypes matchGeneralEventTypes,
            int fromTargetExtra,
            BiConsumer<GeneralEventTypes,T> consumer){
        if (ObjectUtil.checkNull(from, fromFieldClass, fromTargetExtra, consumer)) return;
        findMatchField(from, fromFieldClass, matchGeneralEventTypes, fromTargetExtra,
                (field, eventType) -> {
                    try {
                        field.setAccessible(true);
                        T t = (T) field.get(from);
                        consumer.accept(eventType,t);
                    } catch (Exception ignore) {
                    }
                });
    }

    public static <T> T getSingle(
            Object from,
            Class<T> fromFieldClass,
            GeneralEventTypes generalEventTypes,
            int fromTargetExtra){
        if (ObjectUtil.checkNull(from, fromFieldClass, generalEventTypes, fromTargetExtra)) return null;
        AtomicReference<T> target= new AtomicReference<>();
        findMatchField(from,fromFieldClass, generalEventTypes,fromTargetExtra,(field, eventType1) -> {
            try{
                field.setAccessible(true);
                target.set((T) field.get(from));
            }catch (Exception ignore){}
        });
        return target.get();
    }

    public static <T,U> void transfer(
            Object from,
            Object to,
            Class<T> fromFieldClass,
            Class<U> toFieldClass,
            Function<T,U> function){
        transfer(from,to,fromFieldClass,toFieldClass,BindEventTypeExtra.DEFAULT_EXTRA,BindEventTypeExtra.DEFAULT_EXTRA,function);
    }


    public static <T,U> void transfer(
            Object from,
            Object to,
            Class<T> fromFieldClass,
            Class<U> toFieldClass,
            int fromTargetExtra,
            int toTargetExtra,
            Function<T,U> function){
        transferSingle(from,to,fromFieldClass,toFieldClass,null,fromTargetExtra,toTargetExtra,function);
    }

    public static <T,U> void transferSingle(
            Object from,
            Object to,
            Class<T> fromFieldClass,
            Class<U> toFieldClass,
            GeneralEventTypes matchGeneralEventTypes,
            int fromTargetExtra,
            int toTargetExtra,
            Function<T,U> function){
        if (ObjectUtil.checkNull(from, to, fromFieldClass, toFieldClass, matchGeneralEventTypes, function)) return;
        doSingle(from,to,fromFieldClass,toFieldClass, matchGeneralEventTypes,fromTargetExtra,toTargetExtra,
                (fromField, toField, eventType) -> {
                    try {
                        fromField.setAccessible(true);
                        toField.setAccessible(true);
                        T fromObject = (T) fromField.get(from);
                        U apply = function.apply(fromObject);
                        toField.set(to,apply);
                    } catch (Exception ignore) {
                    }
                });
    }

    public static <T> void setInto(
            Object to,
            Class<? extends T> toFieldClass,
            GeneralEventTypes generalEventTypes,
            int toTargetExtra,
            SetIntoSingleFunction<? extends T> setIntoSingleFunction){
        findMatchField(to,toFieldClass, generalEventTypes,toTargetExtra,
                (field, eventType1) -> {
                    try{
                        T apply = setIntoSingleFunction.apply(eventType1, field);
                        if (apply==null)return;
                        field.setAccessible(true);
                        field.set(to,apply);
                    }catch (Exception ignore){}
                });
    }

    public static <T> void setInto(
            Object to,
            Class<? extends T> toFieldClass,
            int toTargetExtra,
            SetIntoSingleFunction<? extends T> setIntoSingleFunction){
        setInto(to,toFieldClass,null,toTargetExtra,setIntoSingleFunction);
    }

    private static <T,U> void doSingle(
            Object from,
            Object to,
            Class<T> fromFieldClass,
            Class<U> toFieldClass,
            GeneralEventTypes matchGeneralEventTypes,
            int fromTargetExtra,
            int toTargetExtra,
            DoSingleConsumer doSingleConsumer){
        if (ObjectUtil.checkNull(from, to, fromFieldClass, toFieldClass, matchGeneralEventTypes, doSingleConsumer)) return;
        findMatchField(from, fromFieldClass, matchGeneralEventTypes, fromTargetExtra, (fromField, eventType) ->
                findMatchField(to, toFieldClass, eventType, toTargetExtra, (toField,type) ->
                        doSingleConsumer.accept(fromField,toField,type)));
    }

    private static void findMatchField(
            Object from,
            Class<?> matchClass,
            GeneralEventTypes matchGeneralEventTypes,
            int targetExtra,
            BiConsumer<Field, GeneralEventTypes> biConsumer){
        if (ObjectUtil.checkNull(from,matchClass,biConsumer))return;
        Class<?> aClass = from.getClass();
        Field[] declaredFields = aClass.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            Class<?> type = declaredField.getType();
            if (!Objects.equals(type,matchClass))continue;
            BindEventType annotation = declaredField.getDeclaredAnnotation(BindEventType.class);
            if (annotation==null)continue;
            if ((!Objects.equals(annotation.value(), matchGeneralEventTypes))&& matchGeneralEventTypes !=null)continue;
            BindEventTypeExtra extraAnnotation = declaredField.getDeclaredAnnotation(BindEventTypeExtra.class);
            int extra=extraAnnotation==null?BindEventTypeExtra.DEFAULT_EXTRA:extraAnnotation.value();
            if (extra!=targetExtra)continue;
            biConsumer.accept(declaredField,annotation.value());
        }
    }

    public interface SetIntoSingleFunction<T>{
        T apply(GeneralEventTypes generalEventTypes, Field field);
    }

    private interface DoSingleConsumer{
        void accept(Field fromField, Field toField, GeneralEventTypes generalEventTypes);
    }
}
